﻿using System.Windows.Forms;
using System.Collections.Generic;
using System;
using System.IO;
using System.Configuration;
using System.ComponentModel;
using PictureOrganizer2.Database;
using System.Data;
using System.Linq;

namespace PictureOrganizer2.Forms
{
    public partial class ScanForm : Form
    {
        private log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        public string folderPath { get; set; }
        int maxFolderDeep = Convert.ToInt32(ConfigurationManager.AppSettings["MaxFolderDeep"]);
        string fileSearchPattern = ConfigurationManager.AppSettings["FileSearchPattern"];
        char[] fileSearchPatternSeperator = ConfigurationManager.AppSettings["FileSearchPatternSeperator"].ToCharArray();
        List<DirectoryInfo> toScan = new List<DirectoryInfo>();
        DateTime startTime;
        DateTime fileScanStartTime = DateTime.MinValue;
        long fileCount = 0;
        int currentComicId = 0;
        int currentPictureId = 0;
        int currentComicCount = 0;
        int maxComicCount = 0;
        int currentPictureCount = 0;
        int maxPictureCount = 0;

        public ScanForm()
        {
            InitializeComponent();
        }

        private void ScanForm_Load(object sender, EventArgs e)
        {
            worker.RunWorkerAsync();
        }

        private void btn_Cancel_Click(object sender, EventArgs e)
        {
            worker.CancelAsync();
        }

        #region Form-Functions
        delegate void SetTextCallback(Label label, string text);
        private void SetText(Label label, string text)
        {
            if (label.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                try
                {
                    this.Invoke(d, new object[] { label, text });
                }
                catch (ObjectDisposedException ex)
                {
                    logger.Warn("Das aktualisieren des Forms ist gescheitet, da es bereits gelöscht wurde.", ex);
                }
            }
            else
            {
                label.Text = text;
            }
        }

        delegate void SetProgressBarStyleCallback(ProgressBarStyle style);
        private void SetProgressBarStyle(ProgressBarStyle style)
        {
            if (this.progressBar.InvokeRequired)
            {
                SetProgressBarStyleCallback d = new SetProgressBarStyleCallback(SetProgressBarStyle);
                try
                {
                    this.Invoke(d, new object[] { style });
                }
                catch (ObjectDisposedException ex)
                {
                    logger.Warn("Das aktualisieren des Forms ist gescheitet, da es bereits gelöscht wurde.", ex);
                }
            }
            else
            {
                this.progressBar.Style = style;
            }
        }

        delegate void SetStepProgressCallback(int percent);
        private void SetStepProgress(int percent)
        {
            if (this.progressBar_Step.InvokeRequired)
            {
                SetStepProgressCallback d = new SetStepProgressCallback(SetStepProgress);
                try
                {
                    this.Invoke(d, new object[] { percent });
                }
                catch (ObjectDisposedException ex)
                {
                    logger.Warn("Das aktualisieren des Forms ist gescheitet, da es bereits gelöscht wurde.", ex);
                }
            }
            else
            {
                this.progressBar_Step.Value = percent;
            }
        }

        #endregion

        #region Backgroundworker

        private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            logger.Info("Starte des Scanvorgang");
            startTime = DateTime.Now;
            // Style umschalten, da nicht bekannt ist, wieviele Ordner gescannt werden müssen.
            SetText(this.label, "Gefundene Ordner: 0");
            SetProgressBarStyle(ProgressBarStyle.Marquee);
            //progressBar.Style = ProgressBarStyle.Marquee;
            getSubFolders(new DirectoryInfo(folderPath), 0);
            logger.InfoFormat("{0} Ordner wurden gefunden, welche Bilddateien beinhalten.", toScan.Count);
            // Und wieder zurück, da es nun bekannt ist
            SetProgressBarStyle(ProgressBarStyle.Blocks);

            this.currentComicId = Convert.ToInt32(this.comicAdapter.GetLastId() ?? 0) + 1;
            this.currentPictureId = Convert.ToInt32(this.pictureAdapter.GetLastId() ?? 0) + 1;

            this.fileScanStartTime = DateTime.Now;
            this.maxComicCount = toScan.Count;
            foreach (var item in toScan)
            {
                // Prüfen, ob abgebrochen werden soll
                if (worker.CancellationPending) return;

                SetText(this.label, String.Format("Scanne Ordner: {0}", item.FullName));
                scanFolder(item);

                this.currentComicCount++;
            }
        }

        private void getSubFolders(DirectoryInfo folder, int currentDeep)
        {
            // Aktuelle Ordnertiefe erhöhen
            logger.DebugFormat("Der Ordner {0} wird geprüft.", folder.FullName);
            currentDeep++;
            if (currentDeep > maxFolderDeep || worker.CancellationPending)
            {
                // Die maximale Tiefe ist erreicht, daher nun abbrechen.
                // Oder die Verarbeitung soll gestoppt werden
                logger.WarnFormat("Die maximale Ordnertiefe von {0} Ordner(n) wurde erreicht. Tiefere Ordner werden ignoriert.", maxFolderDeep);
                return;
            }

            foreach (DirectoryInfo item in folder.GetDirectories())
            {
                // Alle Unterordner scannen
                getSubFolders(item, currentDeep);
            }

            if (Utility.GetFiles(folder, fileSearchPattern, fileSearchPatternSeperator).Length > 0)
            {
                // Der aktuelle Ordner besitzt Bild-Dateien, daher muss er sich genauer angeschaut werden
                logger.DebugFormat("Der Ordner {0} beinhaltet Bilddateien.", folder.FullName);
                toScan.Add(folder);
                SetText(this.label, String.Format("Gefundene Ordner: {0}", toScan.Count));
                UpdateStatistics(DateTime.Now, DateTime.Now, 0, 0);
            }
        }

        private void scanFolder(DirectoryInfo folder)
        {
            // Erstmal alles leeren
            dbSet.Clear();

            // Zuerst wird geschaut, ob der Ordner schon in der Datenbank existiert

            // Da dies nicht der Fall ist, wird geprüft, ob er eine index-Datei hat, um ihn nach einem
            // Umzug doch noch erkennen zu können
            string comicGUID = Utility.GetComicGUID(folder.FullName);
            if (!string.IsNullOrWhiteSpace(comicGUID))
            {
                comicAdapter.FillByComicKey(dbSet.Comic, comicGUID);

                if (dbSet.Comic.Count == 1)
                {
                    // Wenn der Ordner manuell umgezogen wurde, wird hier ggf. der Path aktualisiert
                    dbSet.Comic[0].FolderPath = folder.Parent.FullName;
                    dbSet.Comic[0].FolderName = folder.Name;
                    if (dbSet.Comic[0].RowState == DataRowState.Modified)
                        comicAdapter.Update(dbSet.Comic);

                    // Da der Comic ja bereits bekannt ist, kann das Scannen nach Bildern übersprungen werden
                    return;
                }

                // Wenn kein Comic in der DB gefunden wird, dann wird es ganz normal angelegt.
            }   


            // Der Ordner ist noch völlig unbekannt und wird daher angelegt.
            DatabaseDataSet.ComicRow comicRow = this.dbSet.Comic.NewComicRow();
            comicRow.ID = currentComicId;
            comicRow.ComicName = folder.Name;
            comicRow.FolderName = folder.Name;
            comicRow.FolderPath = folder.Parent.FullName;
            comicRow.ComicKey = Guid.NewGuid().ToString();
            comicRow.FolderExists = true;
            comicRow.LastExistsCheck = DateTime.Now;
            this.dbSet.Comic.AddComicRow(comicRow);


            FileInfo[] files = Utility.GetFiles(folder, fileSearchPattern, fileSearchPatternSeperator);
            this.currentPictureCount = 0;
            this.maxPictureCount = files.Length;
            decimal averageFileCountPerFolder = (this.fileCount + maxPictureCount) / (this.currentComicCount + 1);
            long maxFileCount = Convert.ToInt64(averageFileCountPerFolder * toScan.Count);


            logger.InfoFormat("Der Ordner {0} beinhaltet {1} Bilder", folder.FullName, files.Length);

            foreach (FileInfo item in files)
            {
                if (worker.CancellationPending) return;

                this.fileCount++;

                // Neues Picture anlegen und bestücken
                DatabaseDataSet.PictureRow pictureRow = this.dbSet.Picture.NewPictureRow();
                pictureRow.ID = currentPictureId;
                pictureRow.ComicID = dbSet.Comic[0].ID;
                pictureRow.FileName = item.Name;
                pictureRow.FileHash = Utility.SHA1StringHash(item.FullName);
                pictureRow.PictureExists = true;
                pictureRow.LastExistsCheck = DateTime.Now;

                // und ins DataSet packen
                this.dbSet.Picture.AddPictureRow(pictureRow);
                this.currentPictureId++;
                currentPictureCount++;

                // Statistik berechnen
                decimal percentPicture = (decimal)currentPictureCount / (decimal)maxPictureCount * 100;
                decimal percentComicStep = 1 / (decimal)maxComicCount * 100;
                decimal percentComic = (decimal)currentComicCount / (decimal)maxComicCount * 100;
                percentComic += percentComicStep * (percentPicture / 100);
                // und ausgeben
                SetStepProgress(Convert.ToInt32(Math.Ceiling(percentPicture)));
                SetText(this.l_percentStep, string.Format("{0:0.00}%", Math.Round(percentPicture, 2)));
                worker.ReportProgress(Convert.ToInt32(Math.Ceiling(percentComic)));
                SetText(this.l_percent, string.Format("{0:0.00}%", Math.Round(percentComic, 2)));
                UpdateStatistics(DateTime.Now, DateTime.Now, maxFileCount, this.fileCount);
            }

            var thumbnail = (from p in dbSet.Picture orderby p.FileName ascending select p.ID).First();
            dbSet.Comic[0].DisplayImageID = thumbnail;
            
            SetText(this.label, "Daten werden gespeichert...");

            // Nachdem nun alles ausgelesen wurde muss das ganze in die Datenbank
            this.comicAdapter.Update(this.dbSet.Comic);
            this.pictureAdapter.Update(this.dbSet.Picture);

            // Und noch die GUID in den Ordner setzen
            Utility.SetComicGUID(Path.Combine(dbSet.Comic[0].FolderPath, dbSet.Comic[0].FolderName), dbSet.Comic[0].ComicKey.ToString());

            this.currentComicId++;
        }

        private void UpdateStatistics(DateTime currentTime, DateTime currentFileScanTime, long maxFileCount, long currentfileCount)
        {
            TimeSpan ts = new TimeSpan(currentTime.Ticks - startTime.Ticks);
            DateTime dt = DateTime.MinValue.Add(ts);
            SetText(this.l_time, dt.ToString("HH:mm:ss"));

            if (this.fileScanStartTime != DateTime.MinValue)
            {
                ts = new TimeSpan(currentFileScanTime.Ticks - fileScanStartTime.Ticks);
                long averageTicksPerFile = ts.Ticks / currentfileCount;
                long estimateTicks = averageTicksPerFile * maxFileCount;
                long restTicks = estimateTicks - ts.Ticks;
                dt = DateTime.MinValue.Add(new TimeSpan(restTicks >= 0 ? restTicks : 0));
                SetText(this.l_eta, dt.ToString("HH:mm:ss"));
            }
        }

        private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.progressBar.Value = e.ProgressPercentage;
        }

        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            TimeSpan ts = new TimeSpan(DateTime.Now.Ticks - this.startTime.Ticks);
            logger.InfoFormat("Der Scanvorgang wurde abgebrochen oder ist beendet.\nZeit: {0} sek\nOrdner: {1}\nBilder:{2}",
                ts.TotalSeconds, this.currentComicCount, fileCount);
            this.Close();
        }

        #endregion

    }
}
